#include "TablaHash.h"

TablaHash* crearTabla(TamBucket tamanioBucket)
{
	TablaHash* tabla = (TablaHash*)malloc(sizeof(TablaHash));
	tabla->tamanioBucket = tamanioBucket;
	tabla->tamanioTabla = 0;
	tabla->Buckets = NULL;
	tabla->BloquesTabla = NULL;
	tabla->cantBuckets = 0;
	return tabla;
}

unsigned int func_hash(TClave clave)
{
    unsigned hash = 0;
    int i;

    unsigned int clave_long = strlen(clave);

    for (i = 0; i < clave_long; i++) {
        hash += clave[i];
        hash += (hash << 10);
        hash ^= (hash >> 6);
    }
    hash += (hash << 3);
    hash ^= (hash >> 11);
    hash += (hash << 15);

    return hash;
}

unsigned int IndiceEnTabla (TClave clave, unsigned int tamanioTabla)
{
	unsigned int clave_hash = func_hash(clave);

	return clave_hash % tamanioTabla;
}



void GuardarBloqueEnTabla (TablaHash* tabla, unsigned int nroBloque, unsigned int dispersion, int indice)
{
	int i,j, cantIndices;
	j = indice;
	cantIndices = tabla->tamanioTabla / dispersion;

	for (i = 0; i < cantIndices; i++)
	{
		div_t bloque;
		bloque = div((j * sizeof(unsigned int)), tabla->tamanioBucket); // DIV
		tabla->BloquesTabla[bloque.quot] = TRUE;
		tabla->Buckets[j] = nroBloque;
		j = (j + dispersion) % tabla->tamanioTabla;
	}
}

int DuplicarTabla (TablaHash* tabla)
{
	unsigned int tamanio = tabla->tamanioTabla;

	// Duplico el tamanio de la tabla.
	tabla->Buckets = (unsigned int*) realloc(tabla->Buckets, sizeof(unsigned int) * (tamanio*2));

	int cantBloquesTablaOrig = getCantBloquesTabla(*tabla);

	//Copio los valores de la tabla en igual sentido en la segunda mitad de la tabla.
	int i,j;
	for(i = tamanio,j=0; i < (2*tamanio); i++,j++) {
		tabla->Buckets[i] = tabla->Buckets[j];
	}

	//Modifico el tamaño de la tabla.

	tabla->tamanioTabla = tamanio * 2;

	int cantBloquesTablaNuevo = getCantBloquesTabla(*tabla);
	if (cantBloquesTablaNuevo > cantBloquesTablaOrig)
	{
		tabla->BloquesTabla = (enum TBOOL *) realloc (tabla->BloquesTabla, cantBloquesTablaNuevo * sizeof(enum TBOOL));
		for (i = cantBloquesTablaOrig; i < cantBloquesTablaNuevo; i++)
		{
			tabla->BloquesTabla[i] = TRUE;
		}
	}

	return OK;
}

int ReducirTabla (TablaHash* tabla, FILE * archTabla)
{
	if (tabla->tamanioTabla < 1) return ERROR;

	//Tabla se queda vacia
	if (tabla->tamanioTabla == 1)
	{
		tabla->tamanioTabla--;

		free(tabla->Buckets);
		tabla->Buckets = NULL;

		free (tabla->BloquesTabla);
		tabla->BloquesTabla = NULL;

		GuardarMetaData(archTabla, tabla);
		//GuardarTabla(archTabla, tabla);
		return OK;
	}

	unsigned int i;
	unsigned int dispersion = tabla->tamanioTabla / 2;

	int cantBloquesTablaOrig = getCantBloquesTabla(*tabla);

	for (i = 0; i < dispersion; i++)
	{
		if (tabla->Buckets[i] != tabla->Buckets[i+dispersion])
			return ERROR;
	}

	tabla->tamanioTabla = dispersion;
	unsigned int * BloquesAux = (unsigned int *) malloc (sizeof (unsigned int) * dispersion);
	for (i = 0; i < dispersion; i++)
	{
		BloquesAux[i] = tabla->Buckets[i];
	}
	free (tabla->Buckets);
	tabla->Buckets = BloquesAux;
	GuardarMetaData(archTabla, tabla);
	//GuardarTabla(archTabla, tabla);

	int cantBloquesTablaNuevo = getCantBloquesTabla(*tabla);
	if (cantBloquesTablaNuevo < cantBloquesTablaOrig)
	{
		enum TBOOL* bloquesTablaAux = (enum TBOOL *) malloc( sizeof(enum TBOOL) * cantBloquesTablaNuevo);
		memcpy(bloquesTablaAux,tabla->BloquesTabla,cantBloquesTablaNuevo);
		free(bloquesTablaAux);
	}

	return OK;
}


void BloqueTablaABytes(TablaHash* tabla, char* bloqueAPersistir, int nroBloque)
{
	//[Bucket i][Bucket i+1]...[Bucket i+CantBucketsPorBloque]

	int longitud = sizeof(TamBucket);
	int actual = 0;
	int cantBuckets = tabla->tamanioBucket / longitud; //siempre da entero

	int i;
	for(i = 0; i < cantBuckets; i++)
	{
		if ((i + nroBloque) == tabla->tamanioTabla) break; // Corte, en caso que el bloque no este lleno

		memcpy(&bloqueAPersistir[actual], &tabla->Buckets[i + nroBloque], longitud);
		actual += longitud;
	}

	memset(&bloqueAPersistir[actual],' ',tabla->tamanioBucket - actual);
}


void MetaDataABytes(TablaHash* tabla, char* metadata)
{
	//[tamanioTabla][tamanioBucket][cantBuckets]

	int longitud = sizeof(unsigned int);
	int actual = 0;
	memcpy(&metadata[actual], &tabla->tamanioTabla, longitud);
	actual += longitud;

	memcpy(&metadata[actual], &tabla->tamanioBucket, longitud);
	actual += longitud;

	memcpy(&metadata[actual], &tabla->cantBuckets, longitud);
	actual += longitud;

	memset(&metadata[actual],' ',LONG_METADATA - actual);
}


int getCantBloquesTabla (TablaHash tabla)
{
	div_t bloques;
	bloques = div((tabla.tamanioTabla * sizeof(unsigned int)), tabla.tamanioBucket); // DIV
	if ((tabla.tamanioTabla > 0) && (bloques.rem > 0))
		return (bloques.quot + 1); // Correccion si algun bloque esta ocupado, pero no lleno.
	return bloques.quot; // El ultimo bloque esta lleno
}


int GuardarTabla (FILE* archTabla, TablaHash* tabla)
{
	int i;
	int cantBloquesTabla = getCantBloquesTabla(*tabla);

	for (i = 0; i < cantBloquesTabla; i++)
	{
		if (tabla->BloquesTabla[i] == TRUE)
		{
			char * bloqueAPersistir = (char*) malloc(sizeof(char) * tabla->tamanioBucket);
			BloqueTablaABytes(tabla, bloqueAPersistir, i);

			// Offset para el bloque a guardar
			int offset = LONG_METADATA + i * tabla->tamanioBucket;

			fseek(archTabla, offset, SEEK_SET);

			int verif = fwrite(bloqueAPersistir, tabla->tamanioBucket, 1, archTabla);
			free(bloqueAPersistir);
			if(verif == 1)
			{
				fflush(archTabla);
			}
			else return ERROR;

			tabla->BloquesTabla[i] = FALSE;
		}
	}
	return OK;
}


int GuardarMetaData (FILE* archTabla, TablaHash* tabla)
{
	char* metadata = (char*) malloc(sizeof(char) * LONG_METADATA);

	MetaDataABytes(tabla, metadata);

	fseek(archTabla, 0, SEEK_SET);

	int verif = fwrite(metadata, LONG_METADATA, 1, archTabla);
	free(metadata);
	if(verif == 1)
	{
		fflush(archTabla);
		return OK;
	}
	return ERROR;
}


TablaHash* LevantarTabla (FILE* archTabla, TamBucket tamanioBucket)
{
	//[tamanioTabla][tamanioBucket][cantBuckets][Bucket1][Bucket2]...[BucketN]

	unsigned int longitud = sizeof(unsigned int);
	unsigned int actual=0;
	int i, j = 0;
	// 'i' se usa para el vector de BloquesTabla, 'j' se usa para el vector de Buckets

	TablaHash* tabla = crearTabla(tamanioBucket);
	char* metadata = (char*) malloc(sizeof(char) * LONG_METADATA);


	fseek (archTabla, 0, SEEK_SET);

	if (fread (metadata, LONG_METADATA, 1, archTabla) == 1) //valida que haya leido correctamente (que el archivo no este vacio)
	{
		memcpy(&tabla->tamanioTabla, &metadata[actual], longitud);
		actual += longitud;

		memcpy(&tabla->tamanioBucket, &metadata[actual], longitud);
		actual += longitud;

		memcpy(&tabla->cantBuckets, &metadata[actual], longitud);
		actual += longitud;
		free (metadata);
		tabla->Buckets = (unsigned int *) malloc(tabla->tamanioTabla * longitud);

		int cantBloquesTabla =  getCantBloquesTabla(*tabla);

		tabla->BloquesTabla = (enum TBOOL *) malloc(cantBloquesTabla * sizeof(enum TBOOL));
		unsigned int cantNrosBucketPorBloque = tabla->tamanioBucket / sizeof(unsigned int);

		for (i = 0; i < cantBloquesTabla; i++) // Levanto de a Bloques desde el archivo
		{
			char * bloqueTabla = (char*) malloc(sizeof(char) * tabla->tamanioBucket); // se usa el mismo tamaño de buckets para los bloques del archivo de Tabla.
			if (fread (bloqueTabla, tabla->tamanioBucket, 1, archTabla) == 1)
			{
				actual = 0;

				int k;
				for (k = 0; k < cantNrosBucketPorBloque; k++)
				{
					memcpy (&tabla->Buckets[j], &bloqueTabla[actual], longitud);
					actual += longitud;
					j++;
					if (j == tabla->tamanioTabla) break; // No hay mas Nros de Buckets para Levantar
				}

				tabla->BloquesTabla[i] = FALSE;
				free (bloqueTabla);
			}
			else
				return NULL;
		}
	}
	else
	{
		free (metadata);
		//Se inicializa el archivo
		GuardarMetaData(archTabla, tabla);
	}

	return tabla;
}

void visualizarTabla(TablaHash* tabla, FILE* salida)
{
	int i;
	fprintf(salida,"Tabla: ");
	for (i=0; i<tabla->tamanioTabla ;i++){
		fprintf(salida,"%d ",tabla->Buckets[i]);
	}
	fprintf(salida,"\nTamanio: %d\n", tabla->tamanioTabla);
}
